﻿using System.Text;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Caching.Distributed;

namespace Devonline.Identity;

public class DefaultKeyRing : ILookupProtectorKeyRing
{
    private readonly object _locker = new();
    private readonly List<DefaultKey> _keyRings;
    private readonly DataProtectionOptions _options;
    private readonly IDistributedCache _cache;

    /// <summary>
    /// 存储于分布式缓存里的钥匙串
    /// </summary>
    /// <param name="cache"></param>
    public DefaultKeyRing(DataProtectionOptions options, IDistributedCache cache)
    {
        _options = options;
        _cache = cache;
        var json = string.Empty;

        lock (_locker)
        {
            json = _cache.GetString(_options.KeyName);
        }

        if (json.IsNotNullOrWhiteSpace())
        {
            _keyRings = json.ToJsonObject<List<DefaultKey>>();
        }
    }

    /// <summary>
    /// Return a specific key
    /// </summary>
    /// <param name="keyId">The id of the key to fetch</param>
    /// <returns>The key ring</returns>
    public string this[string keyId] => _keyRings.FirstOrDefault(x => x.Id == keyId)?.Key;

    /// <summary>
    /// Get the current key id.
    /// </summary>
    public string CurrentKeyId => NewestActivationKey();

    /// <summary>
    /// Return all of the key ids.
    /// </summary>
    /// <returns>All of the key ids</returns>
    public IEnumerable<string> GetAllKeyIds() => _keyRings.Select(x => x.Id);

    /// <summary>
    /// get newest key, if not exist, create new
    /// </summary>
    /// <returns></returns>
    private string NewestActivationKey()
    {
        var now = DateTime.Now;
        var key = _keyRings.Where(x => x.CreateTime <= now && x.ExpireTime >= now).OrderByDescending(x => x.CreateTime).FirstOrDefault();
        if (key == null)
        {
            // 尚不存在就创建出来
            key = new DefaultKey
            {
                Id = KeyGenerator.GetKey<string>(),
                Key = Guid.NewGuid().ToString().GetHashValue<System.Security.Cryptography.SHA256>(),
                CreateTime = now,
                ExpireTime = now.AddDays(_options.ExpireDay),
            };

            _keyRings.Add(key);

            Task.Run(async () =>
            {
                var json = string.Empty;
                lock (_locker)
                {
                    json = _keyRings.ToJsonString();
                }

                await _cache.SetStringAsync(_options.KeyName, json);
            });
        }

        return key.Id;
    }
}
